<?php
namespace Kcdns\Service\Pay\SDK\Driver;

use Kcdns\Service\Pay\SDK\Pay;

class Wxapp extends Pay
{

    const NAVITE_PAY_URL = 'weixin://wxpay/bizpayurl?';
    
//    private $pay_url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
//	private $apiurl_orderquery = 'https://api.mch.weixin.qq.com/pay/orderquery';

	
    protected $gateway = '';
	protected $apidomain = 'https://api.mch.weixin.qq.com/';

    private $apiurl_unifiedorder = 'pay/unifiedorder';
	private $apiurl_orderquery = 'pay/orderquery';

    
    protected $config = array(
        'appid' => '',
        'mchid' => '',
        'key' => '',
        'notify_url' => ''
    );

    /**
     * 支付通知验证
     */
    public function verifyNotify($notify)
    {
		
		$isSign = $notify['sign'] == $this->makeSign($notify) ? true : false;

        if (!$isSign){
			return false;
		}
		$this->checkData($notify);

		if ('return' == I('get.method')) {
			//当同步返回时，获取远程服务器结果，异步时节约一次资源
			$remoteNotify = $this->_orderquery($notify);
		}else{
			$remoteNotify = true;
		}
		if ($remoteNotify) {
			$this->setInfo($notify);
			return true;
		}else{
			return false;
		}
    }

    protected function setInfo($notify) {
        $info = array();
        //支付状态
        $info['status'] = ($notify['result_code'] == 'SUCCESS') ? true : false;
        $info['money'] = $notify['total_fee']/100;
        $info['out_trade_no'] = $notify['out_trade_no'];
        $info['trade_no'] = $notify['transaction_id'];
        $this->info = $info;
    }

	/**
	 * 远程查询订单.
	 * @param   type    $varname    description
	 * @return  type    description
	 * @access  public or private
	 * @static  makes the class property accessible without needing an instantiation of the class
	 * @author hayden <hayden@yeah.net>
	 */
	private function _orderquery($data)
	{
        $param['appid'] = $this->config['appid'];
        $param['mch_id'] = $this->config['mchid'];
        $param['out_trade_no'] = $data['out_trade_no'];
        $param['nonce_str'] = md5(rand(100, 999));
        $param['sign'] = $this->makeSign($param);

        $response = self::getResponse($this->apiurl_orderquery, $param);

        return $response['trade_state']=='SUCCESS';
	} // end func
    /**
     * 微信统一下单
     */
    private function unifiedOrder($paymentInfo=array(),$type='APP'){
        $orderInfo = unserialize($paymentInfo['order_info']);
        $param['body'] = $orderInfo?$orderInfo['desc']:"";
        $param['out_trade_no'] = $paymentInfo['order_no'];
        $param['total_fee'] = $paymentInfo['amount'] * 100;
        $param['time_start'] = date('YmdHis');
        $param['time_expire'] = date('YmdHis', time() + 600);
        $param['goods_tag'] = 'test';
        $param['notify_url'] = $paymentInfo['notify'];
        $param['trade_type'] = $type;
		$param['appid'] = $this->config['appid'];
        $param['mch_id'] = $this->config['mchid'];
        $param['spbill_create_ip'] = $_SERVER['REMOTE_ADDR'];
        $param['nonce_str'] = md5(rand(100, 999));
        $param['sign'] = $this->makeSign($param);

        try{
            $response = self::getResponse($this->apiurl_unifiedorder, $param);
            //预下单信息存储
            $this->_savePrePayInfo($paymentInfo['payment_id'],$param,$response);
            return $response;
        }catch(\Exception $e){
            $this->_savePrePayInfo($paymentInfo['payment_id'],$param,$response,$e->getMessage());
            throw new \Exception($e->getMessage());
        }
    }
    
	
	/**
	 * 常规参数统一效验.
	 * @param   type    $varname    description
	 * @return  type    description
	 * @access  public or private
	 * @static  makes the class property accessible without needing an instantiation of the class
	 * @author hayden <hayden@yeah.net>
	 */
	function checkData($data){
		if ('SUCCESS' !== $data['return_code']) {
			E($data['return_msg']);
		}
		if ('SUCCESS' !== $data['result_code']) {
			E($data['err_code'] . ':' . $data['err_code_des']);
		}
	} // end func
    
    /**
     * 建立提交表单（JsAPI）
     */
    public function buildRequestForm($paymentInfo=array())
    {
        $data = $this->unifiedOrder($paymentInfo);

        $timeStamp = time();
        $app = [
            'appid'     => $this->config['appid'], // 应用ID
            'partnerid' => $this->config['mchid'], // 商户号
            'prepayid'  => $data['prepay_id'],     // 预支付交易会话ID
            'package'   => 'Sign=WXPay',           // 扩展字段
            'noncestr'  => md5(rand(100,999)),     // 随机字符串
            'timestamp' => $timeStamp,             // 时间戳
        ];
        $app['sign'] = $this->makeSign($app);
        return $app;
    }


    /**
     * 异步通知验证成功返回信息
     */
    public function notifySuccess() {
		$return['return_code'] = 'SUCCESS';
		$return['return_msg'] = 'success';
        echo $this->toXml($return);
    }

    /**
     * 将xml转为array
     */
    public static function parseXml($xml='')
    {
		if (!$xml) {
		    E("远程数据异常！");
		}
        libxml_disable_entity_loader(true);
        return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
    }
    
    /**
     * 输出xml字符
     */
    public static function toXml($values)
    {
        $xml = "<xml>";
        foreach ($values as $key => $val) {
            if (is_numeric($val)) {
                $xml .= "<" . $key . ">" . $val . "</" . $key . ">";
            } else {
                $xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
            }
        }
        $xml .= "</xml>";
        return $xml;
    }

    /**
     * 生成签名
     */
    private function makeSign($param)
    {
        // 签名步骤一：按字典序排序参数
        ksort($param);
        reset($param);
        $string = $this->toUrlParams($param);
        // 签名步骤二：在string后加入KEY
        $string = $string . "&key=" . $this->config['key'];

        // 签名步骤三：MD5加密
        $string = md5($string);
        // 签名步骤四：所有字符转为大写
        $result = strtoupper($string);
        return $result;
    }

    /**
     * 格式化参数格式化成url参数
     */
    private function toUrlParams($values)
    {
        $buff = "";
        foreach ($values as $k => $v) {
            if ($k != "sign" && $v != "" && ! is_array($v)) {
                $buff .= $k . "=" . $v . "&";
            }
        }
        
        $buff = trim($buff, "&");
        return $buff;
    }

    /**
     * 配置监检测
     */
    public function check()
    {
        if (! $this->config['appid'] || ! $this->config['mchid'] || ! $this->config['key']) {
            throw new \Exception("微信支付设置有误！");
        }
        return true;
    }
    
	
    /**
     * 微信远程提交
     * @param $data 提交的XML信息
     * @return 下单结果
     */
    protected function getResponse($url, $data) {
        $xml = self::toXml($data);
        $responseTxt = $this->postXmlCurl($xml,$this->apidomain . $url, false ,6 );
		$response = self::parseXml($responseTxt);
		$this->checkData($response);
        return $response;
    }
	/**
     * 以post方式提交xml到对应的接口url
     */
    private function postXmlCurl($xml, $url, $useCert = false, $second = 30)
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch, CURLOPT_HEADER, FALSE);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        curl_setopt($ch, CURLOPT_POST, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);

        //微信cert设置
        //以下两种方式需选择一种
        if($useCert){
            //第一种方法，cert 与 key 分别属于两个.pem文件
            //默认格式为PEM，可以注释
            curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
            curl_setopt($ch,CURLOPT_SSLCERT,$this->_getCertPath().'apiclient_cert.pem');
            //默认格式为PEM，可以注释
            curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');
            curl_setopt($ch,CURLOPT_SSLKEY,$this->_getCertPath().'apiclient_key.pem');
        }
        $data = curl_exec($ch);
        $errNo=curl_errno($ch);
        if (!$errNo) {
            curl_close($ch);
            return $data;
        } else {
            $errInfo=curl_error($ch);
            curl_close($ch);
            throw new \Exception("支付异常curl出错，错误码:$errNo 错误信息:".$errInfo);
        }
    }

    /**
     * 取回信
     * @param $type  notify:异步通知数据  return:同步通知数据
     */
    public function getRequest($type='notify'){
		$iget = I('get.');
		$ipost = I('post.');

        unset($iget['paysn']);
        $response="";
        switch($type){
            //支付同步通知
            case 'return':
                if (IS_POST && !empty($ipost)) {
                    $response = $ipost;
                } elseif (IS_GET && !empty($iget)) {
                    $response = $iget;
                } else {
                    throw new \Exception('Access Denied');
                }
                break;
            //支付异步通知
            case 'notify':
                $notify = file_get_contents('php://input') ?: $GLOBALS['HTTP_RAW_POST_DATA'];
                $response=self::parseXml($notify);
                break;
            //退款同步通知(为本地模拟同步通知）为了解决微信退款在无异步通知情况下 业务流程正常完成
            case 'rreturn':
                $notify = file_get_contents('php://input') ?: $GLOBALS['HTTP_RAW_POST_DATA'];
                $response=self::parseXml($notify);
                break;
            //退款异步通知 需要解密数据
            case 'rnotify':
                $notify = file_get_contents('php://input') ?: $GLOBALS['HTTP_RAW_POST_DATA'];
                $response=self::parseXml($notify);
                break;
            default:
                $response="";
        }

        return $response;
    }
    /**
     * 生成扫码支付url
     * @return string
     */
    public function createNavitePayUrl($payid){
        $params['appid'] = $this->config['appid'];
        $params['mch_id'] = $this->config['mchid'];
        $params['nonce_str'] = md5(time());
        $params['product_id'] = $payid;
        $params['time_stamp'] = time();
		$params['sign'] = $this->makeSign($params);
        
        return self::NAVITE_PAY_URL.http_build_query($params);
    }

    /**
     * 取回异步通知后外部订单ID
     */
    public function getOuterNo($notifyInfo=array(),$type='pay'){
        //支付 返回异步通知的支付方订单号 transaction_id
        //退款 返回异步通知的退款订单号   refund_id
        return $notifyInfo[$type=='pay'?'transaction_id':'refund_id'];
    }

    /**
     * 发起退款
     * @param string $outerNo           支付外部单号
     * @param float  $refundAmount      退款金额(元)
     * @param string $refundCallback    退款回调
     * @param array  $refundRecord      退款记录数据
     * @param array  $paymentRecord     退款对应的支付记录
     * @param string(64) $refundNo      退款单号  同一个订单不同次退款 用于指定退款单号 不传默认用订单号自动生成outerNo-YmdHi
     */
    public function refund($outerNo,$refundAmount,$refundRecord=array(),$paymentRecord=array(),$refundNo=''){
        $param=array(
            'appid'=>$this->config['appid'],
            'mch_id'=>$this->config['mchid'],
            'nonce_str'=>md5(time()),
            //'transaction_id'=>$outerNo,//外部订单号
            'out_refund_no'=>$refundNo?:($refundRecord['order_no'].'-'.date('YmdHi')),//退款单号
            'out_trade_no'=>$refundRecord['order_no'],//商户内部订单号
            'total_fee'=>$paymentRecord['amount']*100,//原订单支付总金额
            'refund_fee'=>$refundAmount*100,//退款金额
        );
        $param['sign'] = $this->makeSign($param);

        if($this->config['mode']==1){
            //调试模式获取测试响应
            $responseTxt = $this->getTestResponse($refundAmount,$paymentRecord);
        }else{
            //中转及正式模式 退款请求
            $xml = self::toXml($param);
            $responseTxt = $this->postXmlCurl($xml,'https://api.mch.weixin.qq.com/secapi/pay/refund', $useCert=true ,6 );
        }
        $response = self::parseXml($responseTxt);

        //记录接口请求及响应
        $this->_savePrePayInfo($refundRecord['payment_id'],$param,$responseTxt);
        if($response['return_code']=='SUCCESS'){
            //退款成功
            return $this->refundSuccess($response['transaction_id'],$response['out_trade_no'],$response['refund_fee']*100);
        }else{
            \KCSLog::WARN($response);
            throw new \Exception('退款失败:'.$response['err_code_des']);
        }
    }

    //退款同步结果返回
    public function refundSuccess($outer_sn,$order_no,$refundAmount){
        return array(
            'outer_sn'=>$outer_sn,
            'order_no'=>$order_no,
            'refund_amount'=>$refundAmount
        );
    }

    /**
     * 退款(模拟)异步通知数据验证支付通知验证
     */
    public function verifyRNotify($notify)
    {

        return $notify['sign'] == $this->makeSign($notify) ? true : false;
    }

    /**
     * @param $amount
     * @return 解密退款异步通知数据 todo
     */
    public function decryptRefundData(){

    }

    //测试支付模拟退款响应
    public function getTestResponse($amount,$paymentInfo){
        $data=array(
            'return_code' =>'SUCCESS',
            'return_msg' =>'OK',
            'appid' =>$this->config['appid'],
            'mch_id' =>$this->config['mchid'],
            'nonce_str' =>time(),
            'transaction_id' =>time(),
            'out_trade_no' =>$paymentInfo['order_no'],
            'out_refund_no' =>time(),
            'refund_id' =>time(),
            'refund_fee' => $amount*100,
        );
        $data['sign'] = $this->makeSign($data);
        return self::toXml($data);
    }

    //获取微信退款所需证书所在路径
    private function _getCertPath(){
        return str_replace('\\','/',__DIR__).'/WeixinCert/';
    }
}